package org.ucas.faker.web.fetcher;

import javafx.util.Pair;
import org.ucas.faker.web.fetcher.cond.*;
import org.ucas.faker.web.fetcher.meta.*;
import org.ucas.faker.web.fetcher.op.*;
import org.ucas.faker.web.fetcher.parse.TableMeta;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class FetchReqBuilder implements Serializable{

  public static final TableMeta meta = new TableMeta();

  private Joiner joiner = new Joiner(meta.graph());

  private AssembleRequest req = new AssembleRequest();

  public static FetchReqBuilder instance() {
    return new FetchReqBuilder();
  }

  private ColumnField getColumn(String name) {
    if("*".equals(name)) return ColumnField.ANY;
    ColumnField column = meta.column(name);
    if(column == null) {
      column = new ColumnField();
      column.column = name;
    }

    if(column.table != null)
      joiner.add(column.table, (column instanceof ConstraintField? ((ConstraintField)column).constraint : null));

    return column;
  }

  private ColumnField joinColumn(ColumnField target, Table table) {
    if(table == null || table.equals(target.table)) return null;
    String key = table.name + "." + target.column;
    ColumnField res = meta.column(key);
    return res.table == null? null : res;
  }


  private void addCondition(FetchCond cond) {
    if(cond == null) return;
    if(req.cond == null) req.cond = cond;
    else if(req.cond instanceof AndCond) {
      AndCond and = (AndCond) req.cond;
      and.add(cond);
    } else {
      AndCond and = new AndCond();
      and.add(req.cond);
      and.add(cond);
      req.cond = and;
    }
  }

  public FetchReqBuilder select(String... names) {
    for(String name:names) {
      ColumnField column = getColumn(name);
      FetchField field = new FetchField(column,null);
      req.fields.add(field);
    }
    return this;
  }

  private FetchReqBuilder select(FetchField... fields) {
    for(FetchField field:fields) {
      ColumnField column = field.column;
      if(column != null && column.table != null)
        joiner.add(column.table,null);
      req.fields.add(field);
    }
    return this;
  }

  private FetchReqBuilder select(ColumnField... fields) {
    for(ColumnField field:fields) {
      if(field == null) continue;
      FetchField tmp = new FetchField(field);
      if(field.table != null)
        joiner.add(field.table,null);
      req.fields.add(tmp);
    }
    return this;
  }

  public FetchReqBuilder selectAs(String key, String name) {
    ColumnField column = getColumn(key);
    FetchField field = new FetchField(column,name);
    req.fields.add(field);
    return this;
  }

  // 聚合字段生成

  public FetchReqBuilder sum(String column, String name) {
    return aggr(column,"SUM",name);
  }

  public FetchReqBuilder avg(String column, String name) {
    return aggr(column,"AVG",name);
  }

  public FetchReqBuilder count(String column, String name) {
    return aggr(column,"COUNT",name);
  }

  public FetchReqBuilder min(String column, String name) {
    return aggr(column,"MIN",name);
  }

  public FetchReqBuilder max(String column, String name) {
    return aggr(column,"MAX",name);
  }

  private FetchReqBuilder aggr(String column, String op, String name) {
    ColumnField col = getColumn(column);
    Aggr aggr = new Aggr(col,op);
    req.fields.add(new FetchField(aggr,name));
    return this;
  }


  // 条件添加

  public <T> FetchReqBuilder lt(String column,T val) {
    return comp(column,"<",val);
  }

  public <T> FetchReqBuilder le(String column,T val) {
    return comp(column,"<=",val);
  }

  public <T> FetchReqBuilder gt(String column,T val) {
    return comp(column,">",val);
  }

  public <T> FetchReqBuilder ge(String column,T val) {
    return comp(column,">=",val);
  }

  public <T> FetchReqBuilder eq(String column,T val) {
    return comp(column,"=",val);
  }

  public <T> FetchReqBuilder not_eq(String column, T val) {
    return comp(column,"<>",val);
  }

  public <T> FetchReqBuilder not_null(String column) {
    CompCond cond = new CompCond();
    cond.comp = "IS";
    cond.column = getColumn(column);
    cond.target = new ConstantField<T>(null);
    addCondition(new NotCond(cond));
    return this;
  }

  private <T> FetchReqBuilder comp(String column, String op, T val) {
    CompCond cond = new CompCond();
    cond.comp = op;
    cond.column = getColumn(column);
    cond.target = new ConstantField<T>(val);
    addCondition(cond);
    return this;
  }

  public <T> FetchReqBuilder range(String column, T lower, T upper) {
    RangeCond cond = new RangeCond();
    cond.column = getColumn(column);
    cond.lower = new ConstantField<T>(lower);
    cond.upper = new ConstantField<T>(upper);
    addCondition(cond);
    return this;
  }

  public <T> FetchReqBuilder in(String column, List<T> interval) {
    EnumCond cond = new EnumCond();
    cond.column = getColumn(column);
    cond.values.values = interval.stream().map(ConstantField::new).collect(Collectors.toList());;
    addCondition(cond);
    return this;
  }

  private void depotTable(Table sub) {
    ColumnField id = meta.column("patent_meta.id");
    Table table = id.table;

    Join join = new Join();
    join.left = sub;
    join.right = table;
    join.cond = new AndCond(new ArrayList<Pair<ColumnField, ColumnField>>(){{
      add(new Pair<ColumnField,ColumnField>(new ColumnField(sub,"patent_id"),id));
    }});
    joiner.merge(join);
  }

  public FetchReqBuilder depot(long depotId, Integer selected) {
    FetchReqBuilder builder = FetchReqBuilder.instance();
    builder.select(new ColumnField("patent_id"),new ColumnField("depot_id")).eq("depot_id",depotId).eq("selected",selected).from("record");
    RequestTable sub = new RequestTable("r",builder.build());
    depotTable(sub);
    return this;
  }

  public FetchReqBuilder depot(long depotId) {
    return depot(depotId,1);
  }

  // 其他操作

  public FetchReqBuilder limit(long start, int total) {
    req.limit = new Limit(start,total);
    return this;
  }

  public FetchReqBuilder limit(int total) {
    return limit(0L,total);
  }

  public FetchReqBuilder order(boolean ascend, String... columns) {
    OrderBy orderBy = new OrderBy();
    orderBy.ascend = ascend;
    for(String column:columns) {
      orderBy.columns.add(getColumn(column));
    }
    req.order = orderBy;
    return this;
  }

  public FetchReqBuilder group(String... columns) {
    GroupBy groupBy = new GroupBy();
    for(String column:columns) {
      groupBy.columns.add(getColumn(column));
    }
    req.group = groupBy;
    return this;
  }

  public FetchReqBuilder top(String column, int num) {
    order(false,column);
    limit(num);
    return this;
  }

  public FetchReqBuilder from(String... names) {
    for(String name:names)
      joiner.add(meta.table(name),null);
    return this;
  }


  public FetchRequest build() {
    req.from = joiner.build();
    return req;
  }

}
